Cysharpの河合様をゲスト講師にお招きしてゲームサーバーに関する社内勉強会を開催しました!
データアナリティクス事業本部の貞松です。
今回はデータ分析でも機械学習でもない話です。
細かい経緯はさておき、ゲーム開発におけるクライアントサイド(主にUnityを想定)だけでなく、サーバーサイドまで一貫してC#で開発することを想定した場合の知見を蓄積する為の社内タスクフォースが発足しました。 その活動の一環でCysharpの河合様とコンタクトを取らせていただき、ゲームサーバーに関する勉強会を実施していただくことになりました。
本記事は、上記勉強会のまとめ的な内容と個人的に重要だと感じたポイント、またゲームサーバーの開発を加速するCysharp製のライブラリについて記載します。
2020.9.16更新 : 当日のスライドをシェアいただいたので追加しました!
株式会社Cysharpについて
今回の勉強会で講師をしていただきました河合様が代表取締役を務められているCysharpのコーポーレートサイトと会社説明の抜粋です。
Cysharpは、プログラミング言語C#の専門集団として「C#の可能性を切り開いていく」ことを掲げ、 Cygamesと共に、Unityに代表されるクライアント技術とサーバーサイドにおけるC#技術の双方を、 パフォーマンスを重視して構築し、ゲーム業界全体の技術やコミュニティの発展に貢献します。
勉強会概要
タスクフォースのメンバーは主にC#による開発経験があったり、なんとなくゲーム開発の話に興味があって寄り集まった為「ゲーム開発におけるサーバーサイドとは?ゲームサーバーとは?」みたいなところから学ぶ必要がありました。
その為勉強会の内容としては、C#云々まずはゲームサーバーとはどういうもので、C#に統一した開発ではどういったメリットや解決すべき課題があるか、といったところにフォーカスしていただきました。
また、課題解決や開発効率化の為に有用なCysharp製のライブラリのお話も絡めて構成していただきました。
当日のスライド
当日のスライドをシェアいただいたので埋め込みで追加しました。
ゲームサーバーの開発について、どういった課題があって、それをどのように解決するか、そこでCysharp製ライブラリがどう活躍するのか、というマッピングについては、このスライドを通して見ていただけるとより理解が深まるかと思います。
勉強会まとめ
アウトゲームとインゲーム
- アウトゲーム: API Server、ゲームのメニュー部分
- インゲーム: Realtime Server、独自のTCP(UDP)通信が多い、WebSocketもある
API Server
- ゲームだからといって特別なことはない。
- 高負荷になりがち、DBへのWriteが比較的多めに発生する。
- 今から選ぶなら非同期処理に専用構文を持つ型付きの言語選択する。
- みんなGoを選びがち。
- RDBMS + Cache(Redis)が好まれる。KeyValue(DynamoDB等)は選ばれない
- なんだかんだでゲームデータは複雑。
- 複数のサーバーに散らかすよりは一箇所で管理したい。
- Writeの頻度が多いのでリードレプリカによるスケールは難しいことが多い。
- DB分割は避けられない
- 垂直分割 vs 水平分割、どっちも大変ではある
- NewSQLの選択肢もある(Google Gloud Spanner、Azure CosmosDB等)が、やはり新規特有の実装上の罠もある。
- 水平を選びたい場合 → 楽
- 垂直分割を選ぶケース → GUIツールを使いたい
- 全階層でのキャッシュの徹底(DB、App、それ以下つらつらと)
- 大抵はRedis
- REST APIにこだわる必要はない。
- 全部POSTでHTTP-RPCスタイルでも問題ない(むしろそれを推奨する)
- サーバー・クライアントの仕様定義の共有が必要。
- IDL(中間言語)を使用する。
- 二重定義が不要になる。
- JSON Schemaを手書きするのは死。
- protoはJSON Schemaの100億倍マシ。
- しかしprotoはC#でない。
- 本体を構成するホスト言語と異なる場合、全ての方が使えない(任意の言語の特性が生かせない)
- じゃあどうするか… → C#をスキーマにしよう
- C# as a Schema
- 実装言語として自然な形式になる。
- プロジェクト参照で済むのでIDL自体のバージョン管理が不要。
- そもそもAPIサーバー必要?
- 認証、課金、クエスト、ミッション、etc…
- 共通の実装で吸収できない個別の機能がある。
- 最高のコンテンツを作る為に
- アプリケーションに特化した作り込みは必要。インゲームだけでなくアウトゲームの作り込みも必要。
- 適切なコードを適切なところに書く。
- とはいえ人モノ金の制限はある。
- その中で作り込みを現実のものにするためのC#
- mBaaSで対応し切るのは難しい(DeNAの事例など)
- mBaaSに合わせてゲーム作るのは向きが違う。
- なぜC#か?
- UnityがC#だからクライアントとサーバーを共通の言語で開発できる。
- とはいえそこにフォーカスしすぎるとうまくいかないことがたくさんある。
- うまくいかない部分をCysharpのライブラリで解決できる。
- パフォーマンスも大事。
- C#は近年パフォーマンス向上に注力。
- ASP.NET Coreは世界最速のWebアプリケーションフレームワーク。
- C#はgRPCでも早い(gRPCも結局は実装次第)
- .NET 5でPure C#のgRPCが追加されており、Rust、C++と同様トップグループである。
- UnityがC#だからクライアントとサーバーを共通の言語で開発できる。
Realtime Server
- 対戦などのインゲーム用サーバー。
- Unityクライアントはデファクトがない。
- 一般的にはPhotonか独自実装が多い。
- ゲームジャンルや規模に応じて最適なソリューションは変わってくるので臨機応変に。
- P2P vs Dedicated Server
- ゲームの場合はP2Pはナシ。そもそもモバイルでは使えない。
- PCゲームならDedicated Serverだとチート対策もある。
- Dedicated Serverはお金が掛かる。
- Dedicated Serverは色々あるがその中から選択する必要がある
- Photon Engine
- Photon Cloud: 簡易なマッチングも面倒みてくれて便利。
- Photon Server: ホスティングを考える必要がある。Windows Serverオンリーなのがネック。
- Monobit Engine
- 独自実装
- WebSocket + ServerApp
- WebSocket + mBaaS → 悪くないが日本での事例が本当にない。
- TCP + ServerApp → MagicOnionはここ。
- TCP + Unity HeadlessApp → エクストリームな実装。パフォーマンスがきつい。そもそもUnityがサーバー向け製品じゃないというのもある。 -Magic Onion Streaming Hub
- 改めてMagicOnion。
- C#で自然にサーバーとクライアントを繋ぐ。
- 別にUnityに依存しない。
- サーバープログラムを透明にしない。
- サーバープログラムをしっかり書くことが前提。
- イベントベース vs ループベース
- MagicOnionのデフォルトはイベントベース。
- LogicLooperはMagicOnionの拡張でループベース。
- Not All in One
- 全部入りでなく、足りない部分は他で補う。
- コンテナ環境に載せる。
- Photon Engine
- リアルタイムサーバーは自作サーバーのホスティングに困る。
- APIサーバーと違ってステートフルなサーバーが基本。
- 対戦ゲームとか、マッチング後に同一サーバーに振り分ける必要があるなど。
- マネージドホスティングへの不満。
- ゲームセッションとコンテナが1:1
- 1room1podでなく、1roomに10podとか詰めたい(主にコスト面で)
- k8sをカッコよく使おうとすると難易度が上がる。
- Other Component
- ASP.NET Core MVC
- ASP.NET Core Blazor → 管理画面等、エンドユーザーに見えない部分ならアリ。
まとめ
- 重要なのは何をつくるか。
- 何を作るかにより選択肢は変わる。
- そのゲームにとって最適なものを選びたい。
- 選定した技術に引っ張られて実現できないのはナシにしたい。
- C#で未来を作っていく。
個人的な重要ポイント
インゲームとアウトゲーム
まずこの概念でサーバーがAPIサーバーとリアルタイムサーバーに分けられるというのがポイントだなと思いました。
日本ではモバイルゲームが盛んで、どちらかというとアウトゲーム(APIサーバー)の作り込みが中心なのかなという印象を受けました。
AWSのGameLift等のmBaaSはインゲーム(リアルタイムサーバー)の支援がメインで、流行りの海外製バトルロイヤルゲーム等々向きな印象です。
現行のAPIサーバーとリプレース
現行のモバイルゲームのAPIサーバーはやはりPHP、Rubyが多く、パフォーマンス面でリプレースを余儀なくされているケースも多いようです。
ここでリプレース先のサーバーサイド言語として、Goが選ばれがちということでした。
流行り廃りの話もありますが、必要な要件を満たす言語がGoかTypeScriptかC#ぐらいに絞られて、元々Microsoftテクノロジーを敬遠しがちな層がGoを選択しているのでは、という感じでした。
.NET Coreも3.x系になって、汎用性やパフォーマンス、機能面が大幅に改善したのを見るに、クライアントサイドがUnity(C#)であるならば、サーバーサイドの開発言語として選択肢に入っても良いのでは、と思いました。
負荷分散の考慮
ゲームサーバーにおいて、大体スパイクするのはCPUなので、負荷分散で考慮すべきはCPUである、という話もちょっと意外なポイントでした。
確かにAPIサーバーに対する小さな多数のリクエストを捌くことが主流であるならば、複雑で巨大な集計をする仕組みと違ってCPUヘビーになりそうだなあと思い直しました。
また、水平・垂直に関わらず、DB分割は実装当初からやっておくべき、というお話もあらかじめ知っておくべき知見だなと思いました。
Cysharp製ライブラリ
Cysharp製ライブラリは、C#(.NET)による開発であれば、ゲーム開発に関わらず有用なライブラリも数多く用意されているので、C#(.NET)開発者の方は必見です。 各ライブラリは、CysharpのGitHubリポジトリで公開されているので、こちらを参照していただければと思います。
と思ったら、MessagePack for C#のみ河合様の個人プロジェクトの方のGitHubリポジトリでした。
以下、勉強会で解説いただいたライブラリについてご紹介します。
- C#最速のシリアライザ
- Redisと相性が良い(Readが多い場合) → LZ4を実装に含めており展開が早い
- クライアント/サーバーが両方C#ならリクエストレスポンスも最適化
- 究極の埋め込み型インメモリDB
- SQLiteより圧倒的に高速
- APIもリアルタイムも両方一つのフレームワークで完結
- HTTP2/gRPCなので高速
- .NET CoreでLinuxでも動作
- ゲームではサーバークライアントが密結合でも良い、という思想
- クライアントもサーバーも自然に繋がっているように見える(ステップ実行で自然に遷移する)
- .NET CoreおよびUnityの為のゼロアロケーションテキスト/構造化ロガー
- IDE上の同一ソリューションでUnityとサーバーのソリューションをマージして同時に扱うことができる
- C#でシナリオを書く負荷テストツール
- k8sでスケールする分散バッチ処理
- クライアントと同じ言語で掛けるのでクライアントの挙動をシミュレートしやすい
- バッチ量産の為のフレームワーク
- バッチコマンドをASP.NET MVC規約の如く、クラス名とメソッド名でコマンドとパラメータバインディングしてくれる
まとめ
単発で1時間の勉強会ということで、ベースの知識を獲得する為の時間としては正直短いかなと思っていたのですが、要点と勘所が絶妙にまとまった最高に学びの多い実りある時間でした。
今後はCysharp製のライブラリのお世話になりつつ、実際に手を動かしながら実装に関するノウハウを貯めていきたいと思います。